package com.xyzboom.compiler.compiler.lr

import com.fasterxml.jackson.annotation.JsonIgnore
import com.xyzboom.compiler.compiler.grammar.ASTNode
import com.xyzboom.compiler.compiler.grammar.ISymbol
import com.xyzboom.compiler.compiler.grammar.Production
import com.xyzboom.compiler.fa.DFA
import mu.KotlinLogging
import javax.script.ScriptContext
import javax.script.ScriptEngine
import javax.script.ScriptEngineManager

private val logger = KotlinLogging.logger {}

/**
 * Sem predict table
 *
 * @constructor Create empty Sem predict table
 */
class SemPredictTable : PredictTable {

    val semantics: ArrayList<String>
    val globalDef: String?

    constructor(
        productions: ArrayList<Production>,
        dfa: DFA<Int, ISymbol>,
        semantics: ArrayList<String>,
        globalDef: String?,
    ) : super(productions, dfa) {
        this.semantics = semantics
        this.globalDef = globalDef
    }

    class SemMapWrapper {
        private val map = HashMap<String, Any?>()
        operator fun get(key: String) = map[key]
        operator fun set(key: String, value: Any?) {
            map[key] = value
        }
    }

    class SemListWrapper(private val value: ArrayList<SemMapWrapper>) {
        operator fun get(index: Int): SemMapWrapper = value[index]
        operator fun set(index: Int, value: SemMapWrapper) {
            this.value[index] = value
        }

        fun add(value: SemMapWrapper) {
            this.value.add(value)
        }
    }


    class StringListWrapper(private val value: ArrayList<String>) {
        operator fun get(index: Int): String = value[index]
        operator fun set(index: Int, value: String) {
            this.value[index] = value
        }

        fun add(value: String) {
            this.value.add(value)
        }
    }

    @JsonIgnore
    private lateinit var engine : ScriptEngine

    override fun onReduce(
        reduceSymbols: ArrayList<ISymbol>, nodeAfter: ASTNode, reduceIndex: Int,
    ): ASTNode {
        if (!this::engine.isInitialized) {
            engine = ScriptEngineManager().getEngineByExtension("kts")!!
        }
        val bindings = engine.createBindings()
//        bindings.putAll(anyOperatorsBinding)
        val nonTerminatorMap = HashMap<String, SemListWrapper>()
        val terminatorMap = HashMap<String, StringListWrapper>()
        for ((index, symbol) in reduceSymbols.withIndex()) {
            if (symbol.isTerminator) {
                terminatorMap.getOrPut(symbol.simpleString()) {
                    StringListWrapper(ArrayList())
                }.add(nodeAfter[index].token!!.info)
            } else {
                nonTerminatorMap.getOrPut(symbol.simpleString()) {
                    SemListWrapper(ArrayList())
                }.apply {
                    if (nodeAfter[index].extra == null) {
                        nodeAfter[index].extra = SemMapWrapper()
                    }
                    add(nodeAfter[index].extra as SemMapWrapper)
                }
            }
        }
        for ((k, v) in nonTerminatorMap) {
            bindings[k] = v
        }
        for ((k, v) in terminatorMap) {
            bindings[k] = v
        }
        engine.setBindings(bindings, ScriptContext.GLOBAL_SCOPE)
        logger.trace { semantics[reduceIndex].trim() }
        if (globalDef != null && globalDef.trim().isNotEmpty())
            engine.eval(globalDef)
        engine.eval(semantics[reduceIndex])

        return nodeAfter
    }
}